/*
 *
 *  *    Copyright 2020-2021 luter.me
 *  *
 *  *    Licensed under the Apache License, Version 2.0 (the "License");
 *  *    you may not use this file except in compliance with the License.
 *  *    You may obtain a copy of the License at
 *  *
 *  *      http://www.apache.org/licenses/LICENSE-2.0
 *  *
 *  *    Unless required by applicable law or agreed to in writing, software
 *  *    distributed under the License is distributed on an "AS IS" BASIS,
 *  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  *    See the License for the specific language governing permissions and
 *  *    limitations under the License.
 *
 */

package com.luter.heimdall.plugins.redis.store;

import com.luter.heimdall.core.authorization.authority.GrantedAuthority;
import com.luter.heimdall.core.authorization.provider.AuthorityDataProvider;
import com.luter.heimdall.core.authorization.provider.NoneAuthoritiesDataProvider;
import com.luter.heimdall.core.authorization.store.AuthorizationStore;
import com.luter.heimdall.core.config.ConfigManager;
import com.luter.heimdall.core.config.HeimdallProperties;
import com.luter.heimdall.core.details.UserDetails;
import com.luter.heimdall.core.exception.HeimdallUnauthorizedException;
import com.luter.heimdall.core.utils.StrUtils;
import lombok.Data;
import lombok.experimental.Accessors;
import org.slf4j.Logger;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.*;
import java.util.concurrent.TimeUnit;

import static org.slf4j.LoggerFactory.getLogger;

/**
 * 权限存储 spring data redis 实现
 *
 * @author luter
 */
@Data
@Accessors(chain = true, fluent = true)
public class RedisAuthorizationStore implements AuthorizationStore {
    /**
     * The constant log.
     */
    private static final transient Logger log = getLogger(RedisAuthorizationStore.class);
    /**
     * 系统权限缓存
     */
    private RedisTemplate<String, Map<String, List<String>>> appCache;
    /**
     * 用户权限缓存
     */
    private RedisTemplate<String, List<? extends GrantedAuthority>> userCache;
    /**
     * 权限数据提供服务
     */
    private AuthorityDataProvider dataProvider;

    public RedisAuthorizationStore() {
    }

    public RedisAuthorizationStore(RedisTemplate<String, Map<String, List<String>>> appCache,
                                   RedisTemplate<String, List<? extends GrantedAuthority>> userCache) {
        this.dataProvider = new NoneAuthoritiesDataProvider();
        this.appCache = appCache;
        this.userCache = userCache;
    }

    /**
     * Instantiates a new Redis authorization store.
     *
     * @param appCache     应用权限缓存
     * @param userCache    用户权限缓存
     * @param dataProvider 外部权限数据提供者,权限初始化的时候，从何处获取初始数据?比如：关系型数据库
     */
    public RedisAuthorizationStore(RedisTemplate<String, Map<String, List<String>>> appCache,
                                   RedisTemplate<String, List<? extends GrantedAuthority>> userCache,
                                   AuthorityDataProvider dataProvider) {
        this(appCache, userCache);
        this.dataProvider = dataProvider;
    }

    @Override
    public boolean isSelfExpired() {
        return true;
    }

    @Override
    public List<? extends GrantedAuthority> getUserAuthorities(UserDetails userDetails) {
        HeimdallProperties config = ConfigManager.getConfig();
        //没登录 ，返回个空的
        if (null == userDetails || StrUtils.isBlank(userDetails.getPrincipal())) {
            return new ArrayList<>();
        }
        log.debug("[getUserAuthorities]::userDetails = [{}]", userDetails);
        String cacheKey = generateUserCacheKey(userDetails.getPrincipal());
        //从 redis 缓存里拿
        List<? extends GrantedAuthority> cachedPerms = userCache.opsForValue().get(cacheKey);
        //缓存里没有
        if (null == cachedPerms || cachedPerms.isEmpty()) {
            //数据提供服务不为空
            if (null != dataProvider) {
                cachedPerms = dataProvider.loadUserAuthorities(userDetails);
                //数据提供者里拿到的也是空的，用户就不具备任何权限
                if (null == cachedPerms || cachedPerms.isEmpty()) {
                    log.warn("The authorities of User:[{}] loaded from meta data service is empty", userDetails.getPrincipal());
                } else {
                    //数据提供者数据不为空，存入缓存
                    userCache.opsForValue().setIfAbsent(cacheKey, cachedPerms, config.getAuthority().getUserCacheExpire(), TimeUnit.SECONDS);
                    log.debug("The user authorities  is successfully " +
                            "loaded from the MetaDataService  " +
                            "and stored in the cache。user:[{}],authorities:[{}]", userDetails, cachedPerms);
                }
            } else {
                log.error("Authorization Metadata Service is not implemented. Failed to obtain user authorities");
            }

        }
        log.debug("The obtained User authorization data is: [{}]", cachedPerms);
        return cachedPerms;

    }

    @Override
    public void putUserAuthorities(String principal, List<? extends GrantedAuthority> authorities) {
        HeimdallProperties config = ConfigManager.getConfig();
        log.debug("[putUserAuthorities]::principal = [{}], authorities = [{}]", principal, authorities);
        if (StrUtils.isBlank(principal)) {
            throw new IllegalArgumentException("The User principal can not be null ");
        }
        if (null == authorities || authorities.isEmpty()) {
            log.warn("The authorities of principal:[{}] is empty and will not be cached", principal);
        } else {
            log.debug("[putUserAuthorities]::principal = [{}], authorities = [{}]", principal, authorities);
            userCache.opsForValue().setIfAbsent(generateUserCacheKey(principal), authorities,
                    config.getAuthority().getUserCacheExpire(), TimeUnit.SECONDS);
        }
    }

    @Override
    public void removeUserAuthorities(UserDetails userDetails) {
        if (null == userDetails || StrUtils.isBlank(userDetails.getPrincipal())) {
            throw new HeimdallUnauthorizedException("The User detail info can not be null");
        }
        if (StrUtils.isBlank(userDetails.getAppId())) {
            throw new IllegalArgumentException("appId can not be null");
        }
        if (StrUtils.isBlank(userDetails.getUserId())) {
            throw new IllegalArgumentException("userId can not be null");
        }
        log.debug("[removeUserAuthorities]::userDetails = [{}]", userDetails);
        final Boolean delete = userCache.delete(generateUserCacheKey(userDetails.getPrincipal()));
        log.info("[removeUserAuthorities]:: userDetails:[{}] userAuthorities = [{}]", userDetails, delete);
    }

    @Override
    public void removeUserAuthorities(String appId, String userId) {
        removeUserAuthorities(new UserDetails(appId, userId));
    }

    @Override
    public void removeAllUserAuthorities() {
        final Set<String> keys = userCache.keys("*");
        if (null != keys && !keys.isEmpty()) {
            userCache.delete(keys);
        }
    }

    @Override
    public Map<String, List<String>> getAppAuthorities(String appId) {
        //校验一下 appId
        if (StrUtils.isBlank(appId)) {
            throw new IllegalArgumentException("The AppId can not be null");
        }
        log.debug("[getAppAuthorities]::appId = [{}]", appId);
        String cacheKey = generateAppCacheKey(appId);
        Map<String, List<String>> appPerms = appCache.opsForValue().get(cacheKey);
        if (null == appPerms || appPerms.isEmpty()) {
            if (null != dataProvider) {
                appPerms = dataProvider.loadAppAuthorities(appId);
                if (null == appPerms || appPerms.isEmpty()) {
                    log.warn("Authorities of appId:[{}] loaded from meta data service is empty", appId);
                } else {
                    HeimdallProperties config = ConfigManager.getConfig();
                    appCache.opsForValue().setIfAbsent(cacheKey, appPerms, config.getAuthority().getAppCacheExpire(), TimeUnit.SECONDS);
                }
            } else {
                log.error("Authorization Metadata Service is not implemented. Failed to obtain system authorities");
            }

        }
        log.debug("Obtained App authorization data is: [{}]", appPerms);
        return appPerms;
    }

    @Override
    public Map<String, List<String>> getAppAuthorities(UserDetails userDetails) {
        if (null == userDetails || StrUtils.isBlank(userDetails.getAppId())) {
            throw new HeimdallUnauthorizedException("The UserDetails or appId can not be null");
        }
        log.debug("[getAppAuthorities]::userDetails = [{}]", userDetails);
        return getAppAuthorities(userDetails.getAppId());
    }

    @Override
    public void putAppAuthorities(String appId, Map<String, List<String>> authorities) {
        HeimdallProperties config = ConfigManager.getConfig();
        if (StrUtils.isBlank(appId)) {
            throw new IllegalArgumentException(" The AppId can not be null ");
        }
        log.debug("[putAppAuthorities]::appId = [{}], authorities = [{}]", appId, authorities);
        if (null == authorities || authorities.isEmpty()) {
            log.warn("The authorities of appId:[{}] is empty and will not be cached", appId);
        } else {
            appCache.opsForValue().setIfAbsent(generateAppCacheKey(appId), authorities,
                    config.getAuthority().getAppCacheExpire(), TimeUnit.SECONDS);
        }
    }

    @Override
    public void removeAppAuthorities(String appId) {
        log.debug("[removeAppAuthorities]::appId = [{}]", appId);
        if (StrUtils.isBlank(appId)) {
            throw new IllegalArgumentException(" The AppId can not be null ");
        }
        appCache.delete(generateAppCacheKey(appId));
    }

    @Override
    public void removeAppAuthorities(UserDetails userDetails) {
        removeAppAuthorities(userDetails.getAppId());
    }

    @Override
    public void removeAllAppAuthorities() {
        final Set<String> keys = appCache.keys("*");
        if (null != keys && !keys.isEmpty()) {
            appCache.delete(keys);
        }
    }

    /**
     * 生成用户缓存 Key
     *
     * @param id the id
     * @return the string
     */
    private String generateUserCacheKey(String id) {
        HeimdallProperties config = ConfigManager.getConfig();
        log.debug("[generateUserCacheKey]::id = [{}]", id);
        return generateCacheKey(config.getAuthority().getUserCacheKeyPrefix(), id);
    }

    /**
     * 生成应用系统缓存 key
     *
     * @param id the id
     * @return the string
     */
    private String generateAppCacheKey(String id) {
        HeimdallProperties config = ConfigManager.getConfig();
        log.debug("[generateAppCacheKey]::id = [{}]", id);
        return generateCacheKey(config.getAuthority().getAppCacheKeyPrefix(), id);
    }

    /**
     * 构造包含前缀和 唯一凭据 的缓存 key
     *
     * @param orgPrefix 设置的前缀
     * @param id        缓存的唯一 ID
     * @return 最终缓存 key
     */
    private String generateCacheKey(String orgPrefix, String id) {
        if (StrUtils.isBlank(id)) {
            throw new IllegalArgumentException("The id can not be null");
        }
        if (StrUtils.isBlank(orgPrefix)) {
            throw new IllegalArgumentException("The prefix can not be null");
        }
        log.debug("[generateCacheKey]::orgPrefix = [{}], id = [{}]", orgPrefix, id);
        String prefix = orgPrefix.trim();
        prefix = prefix.startsWith(StrUtils.COLON) ? prefix.replaceFirst(StrUtils.COLON, StrUtils.EMPTY_STRING) : prefix;
        prefix = prefix.endsWith(StrUtils.COLON) ? prefix : prefix + StrUtils.COLON;
        return prefix + id;
    }
}
